/**
 * 报告样式基类
 *
 * Created by snowingsea on 2021/01/21.
 */
const fs = require('fs-extra');
const path = require('path');
const randomString = require('randomstring');
const ImageModule = require('docxtemplater-image-module-free');
const Pdf = require('./adapter/pdf');
const Docx = require('./adapter/docx');
const Html = require('./adapter/html');
const ProjectVersionSpliter = require('./../project-version-spliter');
const { versionType } = require('./../../project-versions');
const { resultKind, coverageKind } = require('./../../tests');
const customizedInfo = require('./static/material/report.json');

class BaseReporter extends ProjectVersionSpliter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, baseDir = null, format = null) {
    super(versionId, version, project, creator, outputDir);
    this.templateEngine = new Map();
    this.format = format || 'docx';
    this.baseDir = baseDir; // template base dir
    this.i18n = locale;
    this.reportFooterText = this.i18n.__('tests.copyright', { year: (new Date()).getFullYear() });
    this.customizedInfo = customizedInfo;
  }

  /**
   * 生成报告，抽象方法，子类必须实现
   */
  generate() {
    throw new Error('You must implement this function.');
  }

  getReportName() {
    if (this.isIntegrationTest()) {
      return this.i18n.__('tests.integrationTestReport');
    }
    if (this.isStaticAnalyzeTest()) {
      return this.i18n.__('tests.staticAnalyzeTestReport');
    }
    return this.i18n.__('tests.unitTestReport');
  }

  getReportPostfix() {
    // used in unit test & integration test
    return `-${this.format}-${this.complete ? this.i18n.__('tests.complete') : this.i18n.__('tests.streamline')}`;
  }

  generateSummaryReport(data, output, config = {}) {
    let template = 'summary-template';
    if (this.isIntegrationTest()) {
      template = `${template}-it`;
    }
    return this.generateReport(template, data, output, { isSummary: true, ...config });
  }

  generateFileReport(data, output, config = {}) {
    return this.generateReport('file-template', data, output, config);
  }

  replaceBrand(data) {
    if (this.customizedInfo.replaceBrand) {
      data.reportFooterText = this.customizedInfo.copyright.replace('{{year}}', (new Date()).getFullYear());
      data.company = this.customizedInfo.company;
      data.alias = this.customizedInfo.alias;
      data.headerLeft = this.customizedInfo.headerLeft;
    }
  }

  generateReport(template, data, output, {
    isSummary = false,
    baseDir = this.baseDir,
    copyStatic = false,
    generateToc = false,
    imagesDic = [],
  } = {}) {
    data.coverImg = path.join(__dirname, './static/material/cover.jpg');
    data.header = path.join(__dirname, './static/material/header.png');
    this.replaceBrand(data);
    let templateEngine = null;
    const images = new Map();
    Object.keys(data).forEach((key) => {
      if (key.endsWith('Image')) {
        images.set(data[key], key);
      }
    });
    switch (this.format) {
      case 'html':
        return (async () => {
          output = `${output}.html`;
          const staticDir = path.join(this.outputDir, this.project.projectName);
          if (isSummary || copyStatic) {
            const promise = [];
            // 拷贝静态资源
            await fs.copy(path.join(__dirname, 'static'), path.join(staticDir, 'static'));
            Object.keys(data).forEach((key) => {
              if (key.endsWith('Image')) {
                const originalImagePath = data[key];
                const newImagePath = path.join(staticDir, 'static', 'images', path.basename(data[key]));
                data[key] = path.relative(path.dirname(output), newImagePath);
                promise.push(fs.move(originalImagePath, newImagePath));
              }
            });
            imagesDic.forEach(({ target, key, value }) => {
              const newImagePath = path.join(staticDir, 'static', 'images', path.basename(value));
              target[key] = path.relative(path.dirname(output), newImagePath);
              promise.push(fs.move(value, newImagePath));
            });
            await Promise.all(promise);
          }
          data.staticRelativePath = path.relative(path.dirname(output), path.join(staticDir, 'static'));
          if (!this.templateEngine.has(template)) {
            this.templateEngine.set(template, new Html({ template: path.join(baseDir, 'template', 'html', `${template}.ejs`) }));
          }
          templateEngine = this.templateEngine.get(template);
          await templateEngine.generate(
            data,
            output,
          );
          return output;
        })();
      case 'pdf':
        return (async () => {
          data.staticRelativePath = 'static';
          output = `${output}.pdf`;
          if (images.size > 0) {
            images.forEach((key, imagePath) => {
              data[key] = path.basename(imagePath);
            });
          }
          if (imagesDic.length) {
            imagesDic.forEach(({ key, value, target }) => {
              target[key] = path.basename(value);
            });
          }
          if (!this.templateEngine.has(template)) {
            const templates = {
              template: path.join(baseDir, 'template', 'html', `${template}.ejs`),
              staticDir: path.join(baseDir, 'template', 'html', 'static'),
              workDir: path.join(this.outputDir, randomString.generate(16)),
              headerLeft: data.headerLeft,
              headerRight: data.headerRightContent,
              footerCenter: data.reportFooterText,
            };
            if (isSummary) {
              templates.coverTemplate = path.join(baseDir, 'template', 'html', 'cover.ejs');
            }
            this.templateEngine.set(template, new Pdf(templates));
          }
          templateEngine = this.templateEngine.get(template);
          await templateEngine.generate(
            data,
            output,
            [...images.keys(), ...imagesDic.map((i) => i.value)],
          );
          return output;
        })();
      default:
        return (async () => {
          output = `${output}.docx`;
          this.templateEngine.set(template, new Docx(path.join(baseDir, 'template', 'docx', `${template}.docx`)));
          templateEngine = this.templateEngine.get(template);
          // todo: images dic也需要加到ImageModule中去，暂时没有docx报告用到该参数
          if (images.size > 0 || imagesDic.length > 0) {
            templateEngine.getAdapterInstance().attachModule(new ImageModule({
              centered: false,
              getImage: (output) => fs.readFileSync(output),
              getSize: (img, tagValue) => {
                // 优先从imagesDic中获取
                const existsInImagesDic = imagesDic.find((item) => item.value === tagValue);
                if (existsInImagesDic) {
                  return [existsInImagesDic.size.width, existsInImagesDic.size.height];
                }
                if (!images.has(tagValue)) {
                  return [0, 0];
                }
                const imageKey = images.get(tagValue);
                if (!this.getImageSize()[imageKey]) {
                  throw new Error(`Can not get image size of ${imageKey}`);
                }
                return [this.getImageSize()[imageKey].width, this.getImageSize()[imageKey].height];
              },
            }));
          }
          await templateEngine.generate(
            data,
            output,
            generateToc
          );
          return output;
        })();
    }
  }

  getImageSize() {
    return {};
  }

  /**
   * 测试结果数字类型对应的名称
   * @param {Number} kind 测试结果类型
   * @returns {Object}
   */
  static getTestResultKindKey(kind) {
    switch (kind) {
      case resultKind.unknown:
        return 'unknown';
      case resultKind.succeed:
        return 'succeed';
      case resultKind.nonStrictSucceed:
        return 'nonStrictSucceed';
      case resultKind.failed:
        return 'failed';
      case resultKind.noResult:
        return 'noResult';
      case resultKind.runtimeError:
      case resultKind.timeout:
        return 'codeDefect';
      default:
        return 'exception';
    }
  }

  static versionTypeSupport(vType) {
    return vType === versionType.unitTest;
  }

  static codeInstrumentationSupport() {
    return false;
  }

  willTestStatements() {
    return this.version.coverages & coverageKind.statement;
  }

  willTestBranch() {
    return this.version.coverages & coverageKind.branch;
  }

  willTestCondition() {
    return this.version.coverages & coverageKind.condition;
  }

  willTestDecision() {
    return this.version.coverages & coverageKind.decision;
  }

  willTestMcdc() {
    return this.version.coverages & coverageKind.mcdc;
  }
}

/**
 * Export model definition object.
 */
module.exports = BaseReporter;
